/**
 * 作者 fengxingguoyuan
 * Email wnf0000@qq.com
 * **/
(function () {
    var Rule = function (field, ruleName, options, errMessageTemplate) {
        this.field = field;
        this.ruleName = ruleName;
        this.options = options;
        this.errMessageTemplate = errMessageTemplate ?? validator.defaultErrMessage;
        this.adjustErrMessageTemplate = function (defaultErrMessageTemplate) {
            this.errMessageTemplate =
                !this.errMessageTemplate ||
                    this.errMessageTemplate == validator.defaultErrMessage
                    ? defaultErrMessageTemplate
                    : this.errMessageTemplate;
        };
    };
    var Field = function (
        $model,
        fieldPath,
        alias,
        isRequired,
        requiredErrMessage,
        hideErrMessage,
        group,
        always
    ) {
        this.$model = $model;
        this.fieldPath = fieldPath || "";
        this.alias = alias || this.fieldPath.split(".").pop();
        this.isRequired = isRequired === false ? false : true;
        this.requiredErrMessage = requiredErrMessage || `${this.alias}不能为空`;
        this.hideErrMessage = hideErrMessage === true;
        this.group = group;
        this.always = always === true;
        this.errMessage = "";
        this.hasError = function () {
            return this.errMessage != "";
        };
        this.ruleSet = {};
        this.required = function () {
            this.isRequired = true;
            return this;
        };
        for (let method in validator.methods) {
            this[method] = function (options, errMessageTemplate) {
                var rule = new Rule(this, method, options, errMessageTemplate);
                this.ruleSet[method] = rule;
                return this;
            };
        }
        this.getValue = function () {
            let value = this.fieldPath.split(".").reduce((o, c) => {
                if (o) {
                    return o[c];
                }
            }, this.$model);
            return value;
        };
        this.validate = function () {
            let allSuccess = true;
            let fieldObj = this;
            let value = this.getValue();
            if (fieldObj.isRequired && isEmpty(value)) {
                fieldObj.errMessage = validator.formatMsg(
                    fieldObj.requiredErrMessage,
                    fieldObj.alias
                );
                allSuccess = allSuccess && false;
            } else {
                if (!isEmpty(value) || fieldObj.always) {
                    fieldObj.errMessage = "";
                    for (let rName in fieldObj.ruleSet) {
                        let ruleObj = fieldObj.ruleSet[rName];
                        var method = validator.methods[rName];
                        let success = method.call(validator, value, ruleObj);
                        allSuccess = allSuccess && success;
                        if (success) {
                            fieldObj.errMessage = "";
                        } else {
                            fieldObj.errMessage = validator.formatMsg(
                                ruleObj.errMessageTemplate,
                                fieldObj.alias,
                                ruleObj.options
                            );
                            break;
                        }
                    }
                } else {
                    fieldObj.errMessage = "";
                }
            }
            return allSuccess;
        };
    };
    var Model = function (model) {
        this.$model = model;
        this.fieldSet = {};
        this.errorFields = [];
        this.addField = function (
            fieldPath,
            alias,
            isRequired,
            requiredErrMessage,
            hideErrMessage,
            group,
            always
        ) {
            if (fieldPath in this.fieldSet) return this.fieldSet[fieldPath];
            else {
                let field = new Field(
                    this.$model,
                    fieldPath,
                    alias,
                    isRequired,
                    requiredErrMessage,
                    hideErrMessage,
                    group,
                    always
                );
                this.fieldSet[fieldPath] = field;
                return field;
            }
        };
        this.removeField = function (fieldPath) {
            delete this.fieldSet[fieldPath];
            return this;
        };
        this.removeFieldByGroup = function (group) {
            for (let fieldPath in this.fieldSet) {
                if (this.fieldSet[fieldPath].group === group)
                    delete this.fieldSet[fieldPath];
            }
        };

        this.getErrorFields = function () {
            var fs = [];
            for (let field in this.fieldSet) {
                if (this.fieldSet[field]?.hasError()) fs.push(this.fieldSet[field]);
            }
            return fs;
        };
        this.validate = function () {
            let model = this;
            let allSuccess = true;
            for (let field in model.fieldSet) {
                let fieldObj = model.fieldSet[field];
                if (!fieldObj) continue;
                let success = fieldObj.validate();
                allSuccess = allSuccess && success;
            }
            model.errorFields.splice(0);
            model.getErrorFields().forEach((f) => model.errorFields.push(f));
            return allSuccess;
        };
    };
    function isEmpty(value) {
        return (
            value === undefined ||
            value === null ||
            value === "" ||
            value === NaN ||
            value.length === 0
        );
    }
    const validator = {
        models: [],
        defaultErrMessage: "验证未通过",
        formatMsg: function (msgTemplate, alias, options) {
            msgTemplate = msgTemplate.replace("${alias}", alias);
            let pattern = /\${(?<p>[0-9a-zA-Z_]+)}/;
            while ((result = pattern.exec(msgTemplate)) != null) {
                msgTemplate = msgTemplate.replace(
                    "${" + result.groups["p"] + "}",
                    options[result.groups["p"]]
                );
            }
            return msgTemplate;
        },
        methods: {
            zipcode: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}格式不正确";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                return /^\d{6}$/.test(value);
            },
            phone: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}格式不正确";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                return /^0\d{9,11}(\-\d+)?$|^1\d{10}$/.test(value);
            },
            tel: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}格式不正确";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                return /^0\d{9,11}(\-\d+)?$/.test(value);
            },
            mobile: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}格式不正确";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                return /^1\d{10}$/.test(value);
            },
            email: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}格式不正确";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                return /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(value);
            },
            range: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}超出${min}-${max}范围";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                return value >= rule.options.min && value <= rule.options.max;
            },
            lenRange: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}长度超出${min}-${max}范围";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                var len = value.length ?? 0;
                return len >= rule.options.min && len <= rule.options.max;
            },
            equals: function (value, rule) {
                var defaultErrMessageTemplate = "${alias}和${target}不相等";
                rule.adjustErrMessageTemplate(defaultErrMessageTemplate);
                if (typeof rule.options.value == "function")
                    return value === rule.options.value();
                else return value === rule.options.value;
            },
        },
        addModel: function (model) {
            var modelObj = this.models.find((w) => w.$model == model);
            if (modelObj) return modelObj;
            else {
                modelObj = new Model(model);
                this.models.push(modelObj);
                return modelObj;
            }
        },
        getModel: function (model) {
            var modelObj = this.models.find((w) => w.$model == model);
            return modelObj;
        },
        extends: function (ruleName, method) {
            this.methods[ruleName] = method;
        },
        validate: function () {
            let allSuccess = true;
            this.models.forEach((model) => {
                var success = model.validate();
                allSuccess = allSuccess && success;
            });
            return allSuccess;
        },
        getErrorFields: function () {
            return this.models.reduce((arr, m) => {
                return arr.concat(m.getErrorFields());
            }, []);
        },
    };

    window.validator = validator;
})();

const validatorPlugin = {
    install: (app, options) => {
        app.component("validate-model", {
            data() {
                return {
                    //model: this.vldModel,
                };
            },
            props: { vldModel: Object },
            template: `<slot></slot>`,
            provide() {
                return {
                    model: this.vldModel, //Vue.computed(() => this.model),
                };
            },
            created: function () {
                validator.addModel(this.vldModel);
            },
        });

        app.component("validate-input", {
            data() {
                return {
                    field: {},
                };
            },
            inject: ["model"],
            emits: ["update:model-value"],
            props: {
                modelValue: Object,
                vldModel: Object,
                vldField: {
                    type: Object,
                    default: {},
                },
                vldFieldPath: {
                    type: String,
                    required: true,
                },
                vldAlias: String,
                vldRequired: {
                    type: Boolean,
                    default: true,
                },
                vldRequiredErrmessage: String,
                vldGroup: String,
                vldAlways: {
                    type: Boolean,
                    default: false,
                },
                vldHideErrmessage: {
                    type: Boolean,
                    default: false,
                },
            },
            template: `<input v-model="value" v-bind="$attrs" @change="validate" :class="field.hasError()?'validate-error-control':''" />
                  <slot></slot>
      <span class='validate-error-msg' v-if="field.hasError() && !field.hideErrMessage">{{field.errMessage}}</span>`,
            methods: {
                validate: function () {
                    this.field.validate();
                },
            },
            provide() {
                return {
                    // 显式提供一个计算属性
                    field: Vue.computed(() => this.field),
                };
            },
            computed: {
                hasError() {
                    return this.field.hasError();
                },
                value: {
                    get() {
                        return this.modelValue;
                    },
                    set(newValue) {
                        this.$emit("update:model-value", newValue);
                    },
                },
            },
            created: function () {
                var model = validator.addModel(this.model || this.vldModel);
                let {
                    fieldPath = this.vldFieldPath,
                    alias = this.vldAlias,
                    isRequired = this.vldRequired,
                    requiredErrMessage = this.vldRequiredErrmessage,
                    hideErrMessage = this.vldHideErrmessage,
                    group = this.vldGroup,
                    always = this.vldAlways,
                } = this.vldField;

                var f = model.addField(
                    fieldPath,
                    alias,
                    isRequired,
                    requiredErrMessage,
                    hideErrMessage,
                    group,
                    always
                );
                this.field = f;
                model.fieldSet[fieldPath] = this.field;
            },
        });

        app.component("validate-textarea", {
            data() {
                return {
                    field: {},
                };
            },
            inject: ["model"],
            emits: ["update:model-value"],
            props: {
                modelValue: Object,
                vldModel: Object,
                vldField: {
                    type: Object,
                    default: {},
                },
                vldFieldPath: {
                    type: String,
                    required: true,
                },
                vldAlias: String,
                vldRequired: {
                    type: Boolean,
                    default: true,
                },
                vldRequiredErrmessage: String,
                vldGroup: String,
                vldAlways: {
                    type: Boolean,
                    default: false,
                },
                vldHideErrmessage: {
                    type: Boolean,
                    default: false,
                },
            },
            template: `<textarea v-model="value" v-bind="$attrs" @change="validate" :class="field.hasError()?'validate-error-control':''" ></textarea>
                  <slot></slot>
      <span class='validate-error-msg' v-if="field.hasError() && !field.hideErrMessage">{{field.errMessage}}</span>`,
            methods: {
                validate: function () {
                    this.field.validate();
                },
            },
            provide() {
                return {
                    // 显式提供一个计算属性
                    field: Vue.computed(() => this.field),
                };
            },
            computed: {
                hasError() {
                    return this.field.hasError();
                },
                value: {
                    get() {
                        return this.modelValue;
                    },
                    set(newValue) {
                        this.$emit("update:model-value", newValue);
                    },
                },
            },
            created: function () {
                var model = validator.addModel(this.model || this.vldModel);
                let {
                    fieldPath = this.vldFieldPath,
                    alias = this.vldAlias,
                    isRequired = this.vldRequired,
                    requiredErrMessage = this.vldRequiredErrmessage,
                    hideErrMessage = this.vldHideErrmessage,
                    group = this.vldGroup,
                    always = this.vldAlways,
                } = this.vldField;

                var f = model.addField(
                    fieldPath,
                    alias,
                    isRequired,
                    requiredErrMessage,
                    hideErrMessage,
                    group,
                    always
                );
                this.field = f;
                model.fieldSet[fieldPath] = this.field;
            },
        });

        app.component("validate-select", {
            data() {
                return {
                    field: {},
                };
            },
            inject: ["model"],
            emits: ["update:model-value"],
            props: {
                modelValue: Object,
                vldModel: Object,
                vldField: {
                    type: Object,
                    default: {},
                },
                vldFieldPath: {
                    type: String,
                    required: true,
                },
                vldAlias: String,
                vldRequired: {
                    type: Boolean,
                    default: true,
                },
                vldRequiredErrmessage: String,
                vldGroup: String,
                vldAlways: {
                    type: Boolean,
                    default: false,
                },
                vldHideErrmessage: {
                    type: Boolean,
                    default: false,
                },
                dataOptions: Array,
            },

            template: `<select v-model="value" v-bind="$attrs" @change="validate" :class="field.hasError()?'validate-error-control':''" >
              <option v-for="op in dataOptions" :disabled="op.disabled" :value="'value' in op?op['value']:op">{{op['text']??op}}</option>
              </select>
                  <slot></slot>
      <span class='validate-error-msg' v-if="field.hasError() && !field.hideErrMessage">{{field.errMessage}}</span>`,
            methods: {
                validate: function () {
                    this.field.validate();
                },
            },
            provide() {
                return {
                    // 显式提供一个计算属性
                    field: Vue.computed(() => this.field),
                };
            },
            computed: {
                hasError() {
                    return this.field.hasError();
                },
                value: {
                    get() {
                        return this.modelValue;
                    },
                    set(newValue) {
                        this.$emit("update:model-value", newValue);
                    },
                },
            },
            created: function () {
                var model = validator.addModel(this.model || this.vldModel);
                let {
                    fieldPath = this.vldFieldPath,
                    alias = this.vldAlias,
                    isRequired = this.vldRequired,
                    requiredErrMessage = this.vldRequiredErrmessage,
                    hideErrMessage = this.vldHideErrmessage,
                    group = this.vldGroup,
                    always = this.vldAlways,
                } = this.vldField;

                var f = model.addField(
                    fieldPath,
                    alias,
                    isRequired,
                    requiredErrMessage,
                    hideErrMessage,
                    group,
                    always
                );
                this.field = f;
                model.fieldSet[fieldPath] = this.field;
            },
        });

        app.component("validate-removefield", {
            data() {
                return {};
            },
            inject: ["model"],
            props: { vldModel: Object, vldField: Object },
            created: function () {
                var model = validator.getModel(this.model || this.vldModel);
                let { fieldPath } = this.vldField;
                delete model.fieldSet[fieldPath];
            },
        });

        app.component("validate-rule", {
            data() {
                return {
                    //rule: {},
                };
            },
            props: {
                vldRule: {
                    type: Object,
                    default: {},
                },
                vldRuleName: {
                    type: String,
                    required: true,
                },
                vldOptions: Object,
                vldErrmessageTemplate: String,
            },
            inject: ["field"],
            created: function () {
                let {
                    ruleName = this.vldRuleName,
                    options = this.vldOptions,
                    errMessageTemplate = this.vldErrmessageTemplate,
                } = this.vldRule;
                if (typeof this.field[ruleName] == "function")
                    this.field[ruleName](options, errMessageTemplate);
                else console.error(`规则${ruleName}不存在`);
            },
        });

        app.component("validate-errmessage", {
            data() {
                return {
                    field: {},
                };
            },
            inject: ["model"],
            props: { vldFor: String },
            template: `<span class='validate-error-msg' v-if="field?.hasError()">{{field?.errMessage}}</span>`,
            created: function () {
                var f = validator.addModel(this.model).fieldSet[this.vldFor];
                if (f) {
                    this.field = f;
                } else {
                    this.field = { hasError: () => false };
                    let id = setInterval(() => {
                        f = validator.addModel(this.model).fieldSet[this.vldFor];
                        if (f) {
                            clearInterval(id);
                            this.field = f;
                        }
                    }, 100);
                }
            },
        });

        app.component("validate-errsummary", {
            data() {
                return {
                    fields: [],
                };
            },
            inject: ["model"],
            props: { vldFor: String },
            template: `
    <ul v-if="fields.length>0" class="validate-error-summary">
    <li v-for="field in fields"><span class='validate-error-msg'>{{field?.errMessage}}</span></li>
    </ul>
    `,
            created: function () {
                (this.fields = validator.addModel(this.model).errorFields),
                    (validator.addModel(this.model).errorFields = this.fields);
            },
        });

        app.config.globalProperties.validator = window.validator;
        app.config.unwrapInjectedRef = true;
    },
};
